home *** CD-ROM | disk | FTP | other *** search
/ EuroCD 3 / EuroCD 3.iso / Programming / Python1.4_Source / Lib / sunau.py < prev    next >
Text File  |  1998-06-24  |  14KB  |  475 lines

  1. # Stuff to parse Sun and NeXT audio files.
  2. #
  3. # An audio consists of a header followed by the data.  The structure
  4. # of the header is as follows.
  5. #
  6. #    +---------------+
  7. #    | magic word    |
  8. #    +---------------+
  9. #    | header size   |
  10. #    +---------------+
  11. #    | data size     |
  12. #    +---------------+
  13. #    | encoding      |
  14. #    +---------------+
  15. #    | sample rate   |
  16. #    +---------------+
  17. #    | # of channels |
  18. #    +---------------+
  19. #    | info          |
  20. #    |               |
  21. #    +---------------+
  22. #
  23. # The magic word consists of the 4 characters '.snd'.  Apart from the
  24. # info field, all header fields are 4 bytes in size.  They are all
  25. # 32-bit unsigned integers encoded in big-endian byte order.
  26. #
  27. # The header size really gives the start of the data.
  28. # The data size is the physical size of the data.  From the other
  29. # parameter the number of frames can be calculated.
  30. # The encoding gives the way in which audio samples are encoded.
  31. # Possible values are listed below.
  32. # The info field currently consists of an ASCII string giving a
  33. # human-readable description of the audio file.  The info field is
  34. # padded with NUL bytes to the header size.
  35. #
  36. # Usage.
  37. #
  38. # Reading audio files:
  39. #    f = sunau.open(file, 'r')
  40. # where file is either the name of a file or an open file pointer.
  41. # The open file pointer must have methods read(), seek(), and close().
  42. # When the setpos() and rewind() methods are not used, the seek()
  43. # method is not  necessary.
  44. #
  45. # This returns an instance of a class with the following public methods:
  46. #    getnchannels()    -- returns number of audio channels (1 for
  47. #               mono, 2 for stereo)
  48. #    getsampwidth()    -- returns sample width in bytes
  49. #    getframerate()    -- returns sampling frequency
  50. #    getnframes()    -- returns number of audio frames
  51. #    getcomptype()    -- returns compression type ('NONE' for AIFF files)
  52. #    getcompname()    -- returns human-readable version of
  53. #               compression type ('not compressed' for AIFF files)
  54. #    getparams()    -- returns a tuple consisting of all of the
  55. #               above in the above order
  56. #    getmarkers()    -- returns None (for compatibility with the
  57. #               aifc module)
  58. #    getmark(id)    -- raises an error since the mark does not
  59. #               exist (for compatibility with the aifc module)
  60. #    readframes(n)    -- returns at most n frames of audio
  61. #    rewind()    -- rewind to the beginning of the audio stream
  62. #    setpos(pos)    -- seek to the specified position
  63. #    tell()        -- return the current position
  64. #    close()        -- close the instance (make it unusable)
  65. # The position returned by tell() and the position given to setpos()
  66. # are compatible and have nothing to do with the actual postion in the
  67. # file.
  68. # The close() method is called automatically when the class instance
  69. # is destroyed.
  70. #
  71. # Writing audio files:
  72. #    f = sunau.open(file, 'w')
  73. # where file is either the name of a file or an open file pointer.
  74. # The open file pointer must have methods write(), tell(), seek(), and
  75. # close().
  76. #
  77. # This returns an instance of a class with the following public methods:
  78. #    setnchannels(n)    -- set the number of channels
  79. #    setsampwidth(n)    -- set the sample width
  80. #    setframerate(n)    -- set the frame rate
  81. #    setnframes(n)    -- set the number of frames
  82. #    setcomptype(type, name)
  83. #            -- set the compression type and the
  84. #               human-readable compression type
  85. #    setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
  86. #            -- set all parameters at once
  87. #    tell()        -- return current position in output file
  88. #    writeframesraw(data)
  89. #            -- write audio frames without pathing up the
  90. #               file header
  91. #    writeframes(data)
  92. #            -- write audio frames and patch up the file header
  93. #    close()        -- patch up the file header and close the
  94. #               output file
  95. # You should set the parameters before the first writeframesraw or
  96. # writeframes.  The total number of frames does not need to be set,
  97. # but when it is set to the correct value, the header does not have to
  98. # be patched up.
  99. # It is best to first set all parameters, perhaps possibly the
  100. # compression type, and then write audio frames using writeframesraw.
  101. # When all frames have been written, either call writeframes('') or
  102. # close() to patch up the sizes in the header.
  103. # The close() method is called automatically when the class instance
  104. # is destroyed.
  105.  
  106. # from <multimedia/audio_filehdr.h>
  107. AUDIO_FILE_MAGIC = 0x2e736e64
  108. AUDIO_FILE_ENCODING_MULAW_8 = 1
  109. AUDIO_FILE_ENCODING_LINEAR_8 = 2
  110. AUDIO_FILE_ENCODING_LINEAR_16 = 3
  111. AUDIO_FILE_ENCODING_LINEAR_24 = 4
  112. AUDIO_FILE_ENCODING_LINEAR_32 = 5
  113. AUDIO_FILE_ENCODING_FLOAT = 6
  114. AUDIO_FILE_ENCODING_DOUBLE = 7
  115. AUDIO_FILE_ENCODING_ADPCM_G721 = 23
  116. AUDIO_FILE_ENCODING_ADPCM_G722 = 24
  117. AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
  118. AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
  119. AUDIO_FILE_ENCODING_ALAW_8 = 27
  120.  
  121. # from <multimedia/audio_hdr.h>
  122. AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL    # ((unsigned)(~0))
  123.  
  124. _simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
  125.              AUDIO_FILE_ENCODING_LINEAR_8,
  126.              AUDIO_FILE_ENCODING_LINEAR_16,
  127.              AUDIO_FILE_ENCODING_LINEAR_24,
  128.              AUDIO_FILE_ENCODING_LINEAR_32,
  129.              AUDIO_FILE_ENCODING_ALAW_8]
  130.  
  131. Error = 'sunau.Error'
  132.  
  133. def _read_u32(file):
  134.     x = 0L
  135.     for i in range(4):
  136.         byte = file.read(1)
  137.         if byte == '':
  138.             raise EOFError
  139.         x = x*256 + ord(byte)
  140.     return x
  141.  
  142. def _write_u32(file, x):
  143.     data = []
  144.     for i in range(4):
  145.         d, m = divmod(x, 256)
  146.         data.insert(0, m)
  147.         x = d
  148.     for i in range(4):
  149.         file.write(chr(int(data[i])))
  150.  
  151. class Au_read:
  152. ##     access _file, _soundpos, _hdr_size, _data_size, _encoding, \
  153. ##           _sampwidth, _framesize, _framerate, _nchannels, \
  154. ##           _info: private
  155.  
  156.     def __init__(self, f):
  157.         if type(f) == type(''):
  158.             import __builtin__
  159.             f = __builtin__.open(f, 'r')
  160.         self.initfp(f)
  161.  
  162.     def __del__(self):
  163.         if self._file:
  164.             self.close()
  165.  
  166.     def initfp(self, file):
  167.         self._file = file
  168.         self._soundpos = 0
  169.         magic = int(_read_u32(file))
  170.         if magic != AUDIO_FILE_MAGIC:
  171.             raise Error, 'bad magic number'
  172.         self._hdr_size = int(_read_u32(file))
  173.         if self._hdr_size < 24:
  174.             raise Error, 'header size too small'
  175.         if self._hdr_size > 100:
  176.             raise Error, 'header size rediculously large'
  177.         self._data_size = _read_u32(file)
  178.         if self._data_size != AUDIO_UNKNOWN_SIZE:
  179.             self._data_size = int(self._data_size)
  180.         self._encoding = int(_read_u32(file))
  181.         if self._encoding not in _simple_encodings:
  182.             raise Error, 'encoding not (yet) supported'
  183.         if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
  184.               AUDIO_FILE_ENCODING_LINEAR_8,
  185.               AUDIO_FILE_ENCODING_ALAW_8):
  186.             self._sampwidth = 2
  187.             self._framesize = 1
  188.         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
  189.             self._framesize = self._sampwidth = 2
  190.         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
  191.             self._framesize = self._sampwidth = 3
  192.         elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
  193.             self._framesize = self._sampwidth = 4
  194.         else:
  195.             raise Error, 'unknown encoding'
  196.         self._framerate = int(_read_u32(file))
  197.         self._nchannels = int(_read_u32(file))
  198.         self._framesize = self._framesize * self._nchannels
  199.         if self._hdr_size > 24:
  200.             self._info = file.read(self._hdr_size - 24)
  201.             for i in range(len(self._info)):
  202.                 if self._info[i] == '\0':
  203.                     self._info = self._info[:i]
  204.                     break
  205.         else:
  206.             self._info = ''
  207.  
  208.     def getfp(self):
  209.         return self._file
  210.  
  211.     def getnchannels(self):
  212.         return self._nchannels
  213.  
  214.     def getsampwidth(self):
  215.         return self._sampwidth
  216.  
  217.     def getframerate(self):
  218.         return self._framerate
  219.  
  220.     def getnframes(self):
  221.         if self._data_size == AUDIO_UNKNOWN_SIZE:
  222.             return AUDIO_UNKNOWN_SIZE
  223.         if self._encoding in _simple_encodings:
  224.             return self._data_size / self._framesize
  225.         return 0        # XXX--must do some arithmetic here
  226.  
  227.     def getcomptype(self):
  228.         if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
  229.             return 'ULAW'
  230.         elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
  231.             return 'ALAW'
  232.         else:
  233.             return 'NONE'
  234.  
  235.     def getcompname(self):
  236.         if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
  237.             return 'CCITT G.711 u-law'
  238.         elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
  239.             return 'CCITT G.711 A-law'
  240.         else:
  241.             return 'not compressed'
  242.  
  243.     def getparams(self):
  244.         return self.getnchannels(), self.getsampwidth(), \
  245.               self.getframerate(), self.getnframes(), \
  246.               self.getcomptype(), self.getcompname()
  247.  
  248.     def getmarkers(self):
  249.         return None
  250.  
  251.     def getmark(self, id):
  252.         raise Error, 'no marks'
  253.  
  254.     def readframes(self, nframes):
  255.         if self._encoding in _simple_encodings:
  256.             if nframes == AUDIO_UNKNOWN_SIZE:
  257.                 data = self._file.read()
  258.             else:
  259.                 data = self._file.read(nframes * self._framesize * self._nchannels)
  260.             if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
  261.                 import audioop
  262.                 data = audioop.ulaw2lin(data, self._sampwidth)
  263.             return data
  264.         return None        # XXX--not implemented yet
  265.  
  266.     def rewind(self):
  267.         self._soundpos = 0
  268.         self._file.seek(self._hdr_size)
  269.  
  270.     def tell(self):
  271.         return self._soundpos
  272.  
  273.     def setpos(self, pos):
  274.         if pos < 0 or pos > self.getnframes():
  275.             raise Error, 'position not in range'
  276.         self._file.seek(pos * self._framesize + self._hdr_size)
  277.         self._soundpos = pos
  278.  
  279.     def close(self):
  280.         self._file = None
  281.  
  282. class Au_write:
  283. ##     access _file, _framerate, _nchannels, _sampwidth, _framesize, \
  284. ##           _nframes, _nframeswritten, _datawritten, _info, \
  285. ##           _comptype: private
  286.  
  287.     def __init__(self, f):
  288.         if type(f) == type(''):
  289.             import __builtin__
  290.             f = __builtin__.open(f, 'w')
  291.         self.initfp(f)
  292.  
  293.     def __del__(self):
  294.         if self._file:
  295.             self.close()
  296.  
  297.     def initfp(self, file):
  298.         self._file = file
  299.         self._framerate = 0
  300.         self._nchannels = 0
  301.         self._sampwidth = 0
  302.         self._framesize = 0
  303.         self._nframes = AUDIO_UNKNOWN_SIZE
  304.         self._nframeswritten = 0
  305.         self._datawritten = 0
  306.         self._datalength = 0
  307.         self._info = ''
  308.         self._comptype = 'ULAW'    # default is U-law
  309.  
  310.     def setnchannels(self, nchannels):
  311.         if self._nframeswritten:
  312.             raise Error, 'cannot change parameters after starting to write'
  313.         if nchannels not in (1, 2, 4):
  314.             raise Error, 'only 1, 2, or 4 channels supported'
  315.         self._nchannels = nchannels
  316.  
  317.     def getnchannels(self):
  318.         if not self._nchannels:
  319.             raise Error, 'number of channels not set'
  320.         return self._nchannels
  321.  
  322.     def setsampwidth(self, sampwidth):
  323.         if self._nframeswritten:
  324.             raise Error, 'cannot change parameters after starting to write'
  325.         if sampwidth not in (1, 2, 4):
  326.             raise Error, 'bad sample width'
  327.         self._sampwidth = sampwidth
  328.  
  329.     def getsampwidth(self):
  330.         if not self._framerate:
  331.             raise Error, 'sample width not specified'
  332.         return self._sampwidth
  333.  
  334.     def setframerate(self, framerate):
  335.         if self._nframeswritten:
  336.             raise Error, 'cannot change parameters after starting to write'
  337.         self._framerate = framerate
  338.  
  339.     def getframerate(self):
  340.         if not self._framerate:
  341.             raise Error, 'frame rate not set'
  342.         return self._framerate
  343.  
  344.     def setnframes(self, nframes):
  345.         if self._nframeswritten:
  346.             raise Error, 'cannot change parameters after starting to write'
  347.         if nframes < 0:
  348.             raise Error, '# of frames cannot be negative'
  349.         self._nframes = nframes
  350.  
  351.     def getnframes(self):
  352.         return self._nframeswritten
  353.  
  354.     def setcomptype(self, type, name):
  355.         if type in ('NONE', 'ULAW'):
  356.             self._comptype = type
  357.         else:
  358.             raise Error, 'unknown compression type'
  359.  
  360.     def getcomptype(self):
  361.         return self._comptype
  362.  
  363.     def getcompname(self):
  364.         if self._comptype == 'ULAW':
  365.             return 'CCITT G.711 u-law'
  366.         elif self._comptype == 'ALAW':
  367.             return 'CCITT G.711 A-law'
  368.         else:
  369.             return 'not compressed'
  370.  
  371.     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
  372.         self.setnchannels(nchannels)
  373.         self.setsampwidth(sampwidth)
  374.         self.setframerate(framerate)
  375.         self.setnframes(nframes)
  376.         self.setcomptype(comptype, compname)
  377.  
  378.     def getparams(self):
  379.         return self.getnchannels(), self.getsampwidth(), \
  380.               self.getframerate(), self.getnframes(), \
  381.               self.getcomptype(), self.getcompname()
  382.  
  383.     def tell(self):
  384.         return self._nframeswritten
  385.  
  386.     def writeframesraw(self, data):
  387.         self._ensure_header_written()
  388.         nframes = len(data) / self._framesize
  389.         if self._comptype == 'ULAW':
  390.             import audioop
  391.             data = audioop.lin2ulaw(data, self._sampwidth)
  392.         self._file.write(data)
  393.         self._nframeswritten = self._nframeswritten + nframes
  394.         self._datawritten = self._datawritten + len(data)
  395.  
  396.     def writeframes(self, data):
  397.         self.writeframesraw(data)
  398.         if self._nframeswritten != self._nframes or \
  399.               self._datalength != self._datawritten:
  400.             self._patchheader()
  401.  
  402.     def close(self):
  403.         self._ensure_header_written()
  404.         if self._nframeswritten != self._nframes or \
  405.               self._datalength != self._datawritten:
  406.             self._patchheader()
  407.         self._file.flush()
  408.         self._file = None
  409.  
  410.     #
  411.     # private methods
  412.     #
  413. ##     if 0: access *: private
  414.  
  415.     def _ensure_header_written(self):
  416.         if not self._nframeswritten:
  417.             if not self._nchannels:
  418.                 raise Error, '# of channels not specified'
  419.             if not self._sampwidth:
  420.                 raise Error, 'sample width not specified'
  421.             if not self._framerate:
  422.                 raise Error, 'frame rate not specified'
  423.             self._write_header()
  424.  
  425.     def _write_header(self):
  426.         if self._comptype == 'NONE':
  427.             if self._sampwidth == 1:
  428.                 encoding = AUDIO_FILE_ENCODING_LINEAR_8
  429.                 self._framesize = 1
  430.             elif self._sampwidth == 2:
  431.                 encoding = AUDIO_FILE_ENCODING_LINEAR_16
  432.                 self._framesize = 2
  433.             elif self._sampwidth == 4:
  434.                 encoding = AUDIO_FILE_ENCODING_LINEAR_32
  435.                 self._framesize = 4
  436.             else:
  437.                 raise Error, 'internal error'
  438.         elif self._comptype == 'ULAW':
  439.             encoding = AUDIO_FILE_ENCODING_MULAW_8
  440.             self._framesize = 1
  441.         else:
  442.             raise Error, 'internal error'
  443.         self._framesize = self._framesize * self._nchannels
  444.         _write_u32(self._file, AUDIO_FILE_MAGIC)
  445.         header_size = 25 + len(self._info)
  446.         header_size = (header_size + 7) & ~7
  447.         _write_u32(self._file, header_size)
  448.         if self._nframes == AUDIO_UNKNOWN_SIZE:
  449.             length = AUDIO_UNKNOWN_SIZE
  450.         else:
  451.             length = self._nframes * self._framesize
  452.         _write_u32(self._file, length)
  453.         self._datalength = length
  454.         _write_u32(self._file, encoding)
  455.         _write_u32(self._file, self._framerate)
  456.         _write_u32(self._file, self._nchannels)
  457.         self._file.write(self._info)
  458.         self._file.write('\0'*(header_size - len(self._info) - 24))
  459.  
  460.     def _patchheader(self):
  461.         self._file.seek(8)
  462.         _write_u32(self._file, self._datawritten)
  463.         self._datalength = self._datawritten
  464.         self._file.seek(0, 2)
  465.  
  466. def open(f, mode):
  467.     if mode == 'r':
  468.         return Au_read(f)
  469.     elif mode == 'w':
  470.         return Au_write(f)
  471.     else:
  472.         raise Error, "mode must be 'r' or 'w'"
  473.  
  474. openfp = open
  475.